//
//  ATTiledImageView.m
//  ShowLageImage
//
//  Created by lianglibao on 2018/3/3.
//  Copyright © 2018年 lianglibao. All rights reserved.
//

#import "ATTiledImageView.h"
#import <CommonCrypto/CommonDigest.h>

@interface ATTiledImageView ()
@property (nonatomic, strong) NSCache *imagesDic;

@end


@implementation ATTiledImageView{
    UIImage *originImage;
    CGRect imageRect;
    CGFloat imageScale;
}

- (NSCache *)imagesDic {
    if (!_imagesDic) {
        _imagesDic = [[NSCache alloc] init];
    }
    return _imagesDic;
}

+ (Class)layerClass {
    return [CATiledLayer class];
}

- (instancetype)initWithImageInfo:(id)imageInfo
                         andFrame:(CGRect)frame
                     andTileCount:(NSInteger)tileCount {
    self = [self initWithFrame:frame];
    if(self){
        if (tileCount <= 0) {
            tileCount = 4;
        }
        _tileCount = tileCount;
        _imageInfo = imageInfo;
        if (!_imageInfo) {
            return nil;
        }
        
        [self p_checkImageInfo];
    }
    return self;
}

- (void)initSelf {
    @autoreleasepool{
        imageRect = CGRectMake(0.0f,
                               0.0f,
                               CGImageGetWidth(originImage.CGImage),
                               CGImageGetHeight(originImage.CGImage)
                               );
        imageScale = self.frame.size.width / imageRect.size.width;
        CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
        /*
         levelsOfDetail    别名:LOD
         此图层维护的详细级别数
         默认为1,每个细节级别是前一级别的分辨率的一半。
         如果为图层的当前大小指定了太多级别，则会将级别数量限制为最大值（最底层的细节必须至少包含每个维度中的一个像素）
         
         这个值是负责设定视图缩小时的重绘节点, 通过实验确实如此.
         在下面LODB默认值为0时,无论设置LOD的值是多少,在放大图片时，都不会进行重绘操作，视图也就会越来越模糊.
         而对视图进行缩小操作时,根据LOD的值不同zoom scale达到触发重绘的值会相应变化,LOD越大,zoom scale的触发值就越小.
         */
        tiledLayer.levelsOfDetail = 1;
        
        /*
         levelsOfDetailBias 别名:LODB
         此图层的放大细节级别数
         默认为0.每个先前的细节级别是后者的分辨率的两倍。
         例如，指定值2意味着该图层有两个额外的细节级别：2x和4x。
         简单来说,就是从最小视图需要放大多少次,才能达到我们需要的清晰度效果,注意是多少次,一次就是2倍;
         这个和UIScrollview的zoomScale不同，zoomscale表示的是放大多少倍.
         数值越大,视图在放大时可以重新渲染原图的粒度就越细,在失真前视图能呈现的纹理就越清晰,直到显示到像素级别,这时tileSize已经无限接近0了,也就是每个tile负责绘制一个像素;
         绘制视图时会分成多少个tile，除了与我们设置的tileSize有关，还与缩放级别、最大放大层级（也就是levelsOfDetailBias的值）有关
         
         图片相对于视图放大了1/imageScale倍，所以用log2(1/imageScale)得出缩放次数，
         至于为什么要加1，
         是希望图片在放大到原图比例时，还可以继续放大一次（即2倍），可以看的更清晰.
         
         可以通过pow得出最大缩放倍数，例如需要UISCrollView中显示时,可以限定它的最大放大倍数
         CGFloat minimumZoomScale = 1;
         CGFloat maximumZoomScale = pow(2, lev);
         
         */
        int lev = ceil(log2(1/imageScale)) + 1;
        tiledLayer.levelsOfDetailBias = lev;
        
        /*
         tileSize 单个瓦片渲染的区域大小,默认是256x256
         */
        if(self.tileCount > 0) {
            NSInteger tileSizeScale = sqrt(self.tileCount) / 2;
            CGSize tileSize  = self.bounds.size;
            tileSize.width  /= tileSizeScale;
            tileSize.height /= tileSizeScale;
            tiledLayer.tileSize = tileSize;
        }
    }
}

- (void)setFrame:(CGRect)frame {
    [super setFrame:frame];
    imageScale = self.frame.size.width / imageRect.size.width;
    if(self.tileCount>0){
        CATiledLayer *tileLayer = (CATiledLayer *)self.layer;
        CGSize tileSize = self.bounds.size;
        NSInteger tileSizeScale = sqrt(self.tileCount) / 2;
        tileSize.width /= tileSizeScale;
        tileSize.height /= tileSizeScale;
        tileLayer.tileSize = tileSize;
    }
}

- (void)drawRect:(CGRect)rect {
    if (!imageScale || !originImage) {
        return;
    }
    
    // 将视图frame映射到实际图片的frame
    CGRect imageCutRect = CGRectMake(rect.origin.x / imageScale,
                                     rect.origin.y / imageScale,
                                     rect.size.width / imageScale,
                                     rect.size.height / imageScale);
    // 截取指定图片区域,重绘
    @autoreleasepool {
        CGImageRef imageRef = CGImageCreateWithImageInRect(originImage.CGImage, imageCutRect);
        if (!imageRef) {
            return;
        }
        UIImage *tileImage = [UIImage imageWithCGImage:imageRef];
        CGContextRef context = UIGraphicsGetCurrentContext();
        UIGraphicsPushContext(context);
        [tileImage drawInRect:rect];
        UIGraphicsPopContext();
        CGImageRelease(imageRef);
    }
}

- (CGPoint)rectCenter:(CGRect)rect {
    CGFloat centerX = (CGRectGetMaxX(rect)+CGRectGetMinX(rect)) / 2;
    CGFloat centerY = (CGRectGetMaxY(rect)+CGRectGetMinY(rect)) / 2;
    return CGPointMake(centerX, centerY);
}

- (CGSize)returnTileSize {
    return [(CATiledLayer*)self.layer tileSize];
}

// MARK: - setter and getter
- (void)setImageInfo:(id)imageInfo {
    if (!imageInfo) {
        return;
    }
    
    _imageInfo = imageInfo;
    [self p_checkImageInfo];
    [self setNeedsDisplay];
}

- (void)setTileCount:(NSInteger)tileCount {
    _tileCount = tileCount;
    [self initSelf];
    [self setNeedsDisplay];
    //[self setNeedsLayout];
}

- (void)p_checkImageInfo {
    if ([_imageInfo isKindOfClass:UIImage.class]) {
        UIImage *image = (UIImage *)_imageInfo;
        NSString *base64Str = [UIImagePNGRepresentation(image) base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
        NSString *MD5_Str = MD5Str(base64Str);
        if ([self.imagesDic objectForKey:MD5_Str]) {
            // 直接释放内存.
            _imageInfo = nil;
            originImage = [self.imagesDic objectForKey:MD5_Str];
        } else {
            [self.imagesDic setObject:_imageInfo forKey:MD5_Str];
            originImage = _imageInfo;
        }
        
        [self initSelf];
    } else if ([_imageInfo isKindOfClass:NSString.class]) {
        if ([self.imagesDic objectForKey:_imageInfo]) {
            originImage = [self.imagesDic objectForKey:_imageInfo];
            [self initSelf];
        } else {
            originImage = [UIImage imageWithContentsOfFile:_imageInfo];
            if (originImage) {
                [self.imagesDic setObject:originImage forKey:_imageInfo];
                [self initSelf];
            } else {
                NSString *path = [[NSBundle mainBundle] pathForResource:[_imageInfo stringByDeletingPathExtension]
                                                                 ofType:[_imageInfo pathExtension]];
                if (path) {
                    originImage = [UIImage imageWithContentsOfFile:path];
                }
                if (originImage) {
                    [self initSelf];
                }
            }
        }
    }
}

static inline NSString *MD5Str (NSString *sourceStr) {
    const char *cStr = [sourceStr UTF8String];
    unsigned char result[16];
    CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
    
    return [NSString stringWithFormat:
            @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
            result[0], result[1], result[2], result[3],
            result[4], result[5], result[6], result[7],
            result[8], result[9], result[10], result[11],
            result[12], result[13], result[14], result[15]
            ];
}

@end
